﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.IO;
using System.Xml;
using Vortex.Drawing;
using System.Globalization;
using Vortex.Debugging;

namespace Vortex.SceneFramework.CSS {

	/// <summary>
	/// StyleSheet is implementation of extensible CSS-like container for properties
	/// </summary>
	public class StyleSheet {
		///<summary>Selector which describes all elements</summary>
		public static readonly string ALL_SELECTOR = "*";
		public static readonly string ID_PREFIX = "#";
		public static readonly string TYPE_CLASSNAME_SEPARATOR = ".";
		public static readonly char[] CLASSNAME_SEPARATOR = new char[] { ' ' };

		///<summary>Contains lock flag for registering properties</summary>
		private static StyleValue[] _defaultValueArray;	
		private static List<StylePropertyInfo> _propertyList = new List<StylePropertyInfo>();
		private static Dictionary<string, StylePropertyInfo> _propertyMap = new Dictionary<string, StylePropertyInfo>();

		private Dictionary<string, StyleClass> _entityMap = new Dictionary<string,StyleClass>();
		private StyleClass _entity_All;
		private IPropertyResolver _propertyResolver;

		/// <summary>
		/// Initializes a new instance of the <see cref="StyleSheet"/> class.
		/// </summary>
		/// <param name="propertyResolver">The property resolver to use by this style sheet.</param>
		public StyleSheet(IPropertyResolver propertyResolver) {
			_propertyResolver = propertyResolver;
		}

		/// <summary>
		/// Resolves the property value in context of spritesheet.
		/// </summary>
		/// <param name="property">The property to resolve.</param>
		/// <param name="value">The string value to resolve.</param>
		/// <returns>Resolved property object</returns>
		public object ResolveValue(StyleKey property, string value) {
			if (null == value) throw new ArgumentNullException("value");

			StylePropertyInfo propertyInfo = GetPropertyInfo(property);
			return _propertyResolver.ResolveProperty(propertyInfo.Name, propertyInfo.ValueType, value);
		}

		#region Static Methods

		/// <summary>
		/// Registers the property to use in style sheet.
		/// </summary>
		/// <param name="propertyName">Name of the property.</param>
		/// <param name="valueType">Type of the value.</param>
		/// <returns>A <see cref="StyleProperty"/> object which describes this property</returns>
		public static StyleKey RegisterProperty<T>(string propertyName, T defaultValue) {
			if (string.IsNullOrEmpty(propertyName)) throw new ArgumentException("Property name can't be null or empty", "propertyName");

			if (null != _defaultValueArray) {
				throw new StyleSheetException("StyleSheet can't register property '{0}' because register call is invoked after the first style value collection created. All of properties should be registered before first style will be created.", propertyName);
			}

			if (_propertyMap.ContainsKey(propertyName)) {
				throw new StyleSheetException("Property {0} is going to be registered second time", propertyName);
			}
			//generate new property index
			int propertyIndex = _propertyList.Count;
			//add new map
			StylePropertyInfo propertyInfo = new StylePropertyInfo(propertyIndex, propertyName, typeof(T), defaultValue);
			_propertyList.Add(propertyInfo);
			_propertyMap.Add(propertyName, propertyInfo);

			return new StyleKey(propertyName);
		}

		#endregion

		#region Properties

		/// <summary>
		/// Gets the style property count.
		/// </summary>
		/// <value>The style property count.</value>
		public static int PropertyCount {
			get { return _propertyList.Count; }
		}

		#endregion

		/// <summary>
		/// Gets the Indexes the of property.
		/// </summary>
		/// <param name="propertyName">Name of the property.</param>
		/// <returns></returns>
		public static int IndexOf(string propertyName) {
			if (null == propertyName) throw new ArgumentNullException("propertyName");

			StylePropertyInfo propertyInfo;
			if (_propertyMap.TryGetValue(propertyName, out propertyInfo)) {
				return propertyInfo.Index;
			} else {
				throw new StyleSheetException("Style property '{0}' is not registered and can't be resolved", propertyName);
			}
		}

		/// <summary>
		/// Gets the property info by specified property index. Index should be greater or equal zero and be less than PropertyCount
		/// </summary>
		/// <param name="propertyIndex">Index of the property.</param>
		/// <returns>A <see cref="StylePropertyInfo"/> instance with specified index.</returns>
		public static StylePropertyInfo GetPropertyInfo(int propertyIndex) {
			return _propertyList[propertyIndex];
		}

		/// <summary>
		/// Gets the property info for specified property name.
		/// </summary>
		/// <param name="property">Style property.</param>
		/// <returns>A <see cref="StylePropertyInfo"/> instance with specified name.</returns>
		public static StylePropertyInfo GetPropertyInfo(StyleKey property) {
			if (null == property) throw new ArgumentNullException("property");

			return GetPropertyInfo(property.Index);
		}

		/// <summary>
		/// Gets the <see cref="Vortex.SceneFramework.CSS.StyleEntity"/> for the specified selector.
		/// </summary>
		/// <value>Style entity instance if it exists; or <c>null</c> if entity doesn't exist</value>
		public StyleClass this[string entitySelector] {
			get {
				StyleClass styleClass;
				if (TryGetEntity(entitySelector, out styleClass)) {
					return styleClass;
				} else {
					return null;//throw new StyleSheetException("Style class '{0}' doesn't exists in current style sheet", className);
				}
			}
		}

		/// <summary>
		/// Tries to get style entity for the specified selector
		/// </summary>
		/// <param name="entitySelector">The style entity selector.</param>
		/// <param name="styleEntity">The retreived style entity.</param>
		/// <returns><c>true</c> if style entity for specified selector exists; otherwise <c>false</c></returns>
		public bool TryGetEntity(string entitySelector, out StyleClass styleEntity) {
			if (null == entitySelector) throw new ArgumentNullException("entitySelector");

			return _entityMap.TryGetValue(entitySelector, out styleEntity);
		}

		/// <summary>
		/// Determines whether this sheet contains style entity for the specified selector.
		/// </summary>
		/// <param name="entitySelector">Style entity selector.</param>
		/// <returns>
		/// 	<c>true</c> if sheet contains style entity for the specified selector; otherwise, <c>false</c>.
		/// </returns>
		public bool ContainsEntity(string entitySelector) {
			if (null == entitySelector) throw new ArgumentNullException("entitySelector");

			return _entityMap.ContainsKey(entitySelector);
		}

		/// <summary>
		/// Creates the style entity for the specified selector.
		/// </summary>
		/// <param name="entitySelector">Style entity selector.</param>
		/// <returns>Newly created style entity</returns>
		private StyleClass CreateEntity(string entitySelector) {
			if (null == entitySelector) throw new ArgumentNullException("entitySelector");

			StyleClass collection = new StyleClass(this, entitySelector);
			_entityMap.Add(entitySelector, collection);

			//speed up access to all collection
			if (entitySelector == ALL_SELECTOR) {
				_entity_All = collection;
			}
			return collection;
		}

		/// <summary>
		/// Gets or create the new style entity for specified selector.
		/// </summary>
		/// <param name="entitySelector">The style entity selector.</param>
		/// <returns>Existing for newly created style entity</returns>
		public StyleClass GetOrCreateEntity(string entitySelector) {
			StyleClass styleClass;
			if (TryGetEntity(entitySelector, out styleClass)) {
				return styleClass;
			} else {
				return CreateEntity(entitySelector);
			}
		}

		#region Style Sheet Loading


		/// <summary>
		/// Parses the CSS file.
		/// </summary>
		/// <param name="fileName">Name of the CSS file.</param>
		public void ParseCSSFile(string fileName) {
			if (null == fileName) throw new ArgumentNullException("fileName");

			using (Log.TraceUp("Parsing CSS file '{0}'", fileName)) {
				ResourceFileInfo file = new ResourceFileInfo(fileName);
				ParseCSS(file.GetContentAsText());
			}
		}

		/// <summary>
		/// Parses the CSS text format.
		/// </summary>
		/// <param name="cssText">The CSS text.</param>
		public void ParseCSS(string cssText) {
			if (null == cssText) throw new ArgumentNullException("cssText");

			//trace information
			Log.Trace("Parsing CSS fragment");
			Log.Details("{0}", cssText);

			CssParser loader = new CssParser(this);
			loader.Parse(cssText);
		}

		#endregion

		/// <summary>
		/// Gets the enumeration of element style value collections which are available for element because of matching selectors.
		/// </summary>
		/// <param name="styledElement">The styled element.</param>
		/// <returns>Enumeration of style value collections matched element selectors and state</returns>
		public IEnumerable<StyleValueSet> GetElementStyles(IStyledNode styledElement) {
			foreach (StyleClass entity in GetElementEntities(styledElement)) {
				//check element undefined state
				StyleValueSet valueSet = entity[StyleState.Undefined];
				if (null != valueSet) {
					yield return valueSet;
				}
				//go through all of element states
				foreach (StyleState state in styledElement.GetElementStates()) {
					valueSet = entity[state];
					if (null != valueSet) {
						yield return valueSet;
					}
				}
			}
		}

		/// <summary>
		/// Gets the enumeration of element entities matching element selectors.
		/// </summary>
		/// <param name="styledElement">The styled element.</param>
		/// <returns>Enumeration of style entities matching element selectors</returns>
		private IEnumerable<StyleClass> GetElementEntities(IStyledNode styledElement) {
			StyleClass entity;

			if (null != _entity_All) {
				yield return _entity_All;
			}

			string typeName = styledElement.GetType().Name;
			if (TryGetEntity(typeName, out entity)) {
				yield return entity;
			}

			string elementId = styledElement.Id;
			if (!string.IsNullOrEmpty(elementId)) {
				if (TryGetEntity(ID_PREFIX + elementId, out entity)) {
					yield return entity;
				}
			}

			string className = styledElement.Class;
			if (!string.IsNullOrEmpty(className)) {
				foreach (string name in className.Split(CLASSNAME_SEPARATOR, StringSplitOptions.RemoveEmptyEntries)) {
					string classSelector = TYPE_CLASSNAME_SEPARATOR + name;

					if (TryGetEntity(classSelector, out entity)) {
						yield return entity;
					}

					if (TryGetEntity(typeName + classSelector, out entity)) {
						yield return entity;
					}
				}
			}
		}

		/// <summary>
		/// Creates the default style value array.
		/// </summary>
		/// <returns>Default value arrau</returns>
		internal static StyleValue[] CreateDefaultValueArray() {
			if (null == _defaultValueArray) {
				_defaultValueArray = new StyleValue[_propertyList.Count];
				for (int n = 0; n < _defaultValueArray.Length; ++n) {
					_defaultValueArray[n] = new StyleValue(_propertyList[n].DefaultValue, StyleValue.DEFAULT_IMPORTANCE);
				}
			}
			return (StyleValue[])_defaultValueArray.Clone();
		}

		/// <summary>
		/// Checks the index of the style property.
		/// </summary>
		/// <param name="index">The index of style property.</param>
		/// <returns><c>true</c> if style is in valid propety index range; otherwise <c>false</c></returns>
		internal static bool CheckPropertyIndex(int index) {
			return index >= 0 && index < _propertyList.Count;
		}
	}

}
